import { EventEmitter } from 'events';

export type TaskFunction = (workerId: string) => Promise<any>;

export const enum AsyncTaskWorkerState { READY, RUNNING, DEAD }

export class AsyncTaskWorker extends EventEmitter {
    private _id: string;
    private _state: AsyncTaskWorkerState;

    public constructor(id: string) {
        super();

        this._id = id;
        this._state = AsyncTaskWorkerState.READY;
    }

    public get id() {
        return this._id;
    }

    public get state() {
        return this._state;
    }

    public get isReady() {
        return this._state === AsyncTaskWorkerState.READY;
    }

    public get isRunning() {
        return this._state === AsyncTaskWorkerState.RUNNING;
    }

    public get isDead() {
        return this._state === AsyncTaskWorkerState.DEAD;
    }

    public async run(task: TaskFunction) {
        if (this.isReady) {
            this._state = AsyncTaskWorkerState.RUNNING;
            this.emit('running', this);

            try {
                await task(this.id);
            } catch (e) {
                this._state = AsyncTaskWorkerState.DEAD;
                this.emit('dead', e, this);
                return;
            }

            this._state = AsyncTaskWorkerState.READY;
            this.emit('ready', this);
            return;
        }

        if (this.isRunning) {
            throw new Error(`[AsyncTaskRunnerWorker]: worker '${this.id}' is already running`);
        }

        if (this.isDead) {
            throw new Error(`[AsyncTaskRunnerWorker]: worker '${this.id}' is dead`);
        }
    }
}

export interface AsyncTaskWorker {
    on(event: 'dead', listener: (error: Error, worker: AsyncTaskWorker) => any): this;
    on(event: 'ready' | 'running', listener: (worker: AsyncTaskWorker) => any): this;
    once(event: 'dead', listener: (error: Error, worker: AsyncTaskWorker) => any): this;
    once(event: 'ready' | 'running', listener: (worker: AsyncTaskWorker) => any): this;
}

export default AsyncTaskWorker;
